package convert

import (
	"bufio"
	"context"
	"fmt"
	"gitee.com/general252/ffmpeg-tool/help"
	"io"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

// Audio 音频转换
// -ar 设置采样率
// -ac 设置声音的声道数
// -acodec (-c:a)设置声音编解码器，未设定时则使用与输入流相同的编解码器，一般后面加 copy 表示拷贝。
// -b:a 32K
// -an 不处理音频
// 采样率，是指在数字音频中采集和处理声音信号时，对声音信号进行采样的频率。采用率使用赫兹（Hz）来表示，即表示每秒采集多少个样本。
// 码率，指的是数字音频中的数据传输速率，是采样率和位深的乘积，使用比特率（bps）表示。
// 位深，指的是数字音频中每个采样点所占的比特数。位深决定了数字音频的动态范围，即声音信号的最大振幅和最小振幅之间的距离。
//
// ffmpeg -ss 00:18:40 -t 00:04:00 -i "小破球2.HD1080p.国语中字.mp4" -c:a pcm_alaw -vn -ar 8000 -ac 1 -f alaw output3.g711a
// ffplay -i file.pcm   -f s16le -ac 1 -ar 8000
// ffplay -i file.g711a -f alaw  -ac 1 -ar 8000
// ffplay -i file.g711u -f mulaw -ac 1 -ar 8000
// 播放G726
// ## 小端 <ASF类型>
// ffplay -f g726le -ar 8000 -ac 1 -code_size 2 -i xxx.g726
//
// ## 大端 <RFC3551类型>
// ffplay -f g726 -ar 8000 -ac 1 -code_size 2 -i xxx.g726
//
// -f #格式
// -ac #音频通道
// -ar #采样率
// -code_size #采样宽度 取值范围 2~5 分别代表 16kbps 24kbps 32kbps 40kbps
type Audio struct {
}

func NewAudio() *Audio {
	return &Audio{}
}

type AudioCodec string

const (
	MP3  AudioCodec = "mp3"
	AAC  AudioCodec = "aac"
	OPUS AudioCodec = "opus"
	G726 AudioCodec = "g726"
	PCMA AudioCodec = "pcma" // g711a
	PCMU AudioCodec = "pcmu"
	PCM  AudioCodec = "pcm"
)

// Convert 转换音频格式
// sampleRate: 采样率
// format: 格式, aac/opus(libopus)
// channel: 通道数
// bitRate: 常用的比特率有: 96k, 112k, 128k, 160k, 192k, 256k, 320k
// 8,000 Hz - 电话所用采样率, 对于人的说话已经足够
// 11,025 Hz-AM调幅广播所用采样率
// 22,050 Hz和24,000 Hz- FM调频广播所用采样率
// 32,000 Hz - miniDV 数码视频 camcorder、DAT (LP mode)所用采样率
// 44,100 Hz - 音频 CD, 也常用于 MPEG-1 音频（VCD, SVCD, MP3）所用采样率
// 47,250 Hz - 商用 PCM 录音机所用采样率
// 48,000 Hz - miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率
// 50,000 Hz - 商用数字录音机所用采样率
// 96,000 或者 192,000 Hz - DVD-Audio、一些 LPCM DVD 音轨、BD-ROM（蓝光盘）音轨、和 HD-DVD （高清晰度 DVD）音轨所用所用采样率
// 2.8224 MHz - Direct Stream Digital 的 1 位 sigma-delta modulation 过程所用采样率。
func (tis *Audio) Convert(ctx context.Context, inFile, outFile string,
	start time.Duration, duration time.Duration,
	codec AudioCodec, sampleRate int, channel int, bitRate string,
	onmessage func(msg string)) (string, error) {
	// ffmpeg -i "兰亭序 - 周杰伦.mp3" -ar 48000  -c:a libopus -f opus  wav_output

	var (
		audioCodec = string(codec)
		format     = string(codec)
	)

	switch codec {
	case MP3:
		if len(filepath.Ext(outFile)) == 0 {
			outFile += ".mp3"
		}
	case AAC:
		audioCodec = "aac" // libfdk_aac
		format = ""
		if strings.ToLower(filepath.Ext(outFile)) != ".aac" {
			outFile += ".aac"
		}
		// -b:a 128k
	case OPUS:
		sampleRate = 48000
		audioCodec = "libopus"
		format = "opus"
		if len(filepath.Ext(outFile)) == 0 {
			outFile += ".opus"
		}
	case G726:
		sampleRate = 8000
		format = "g726"
		if len(filepath.Ext(outFile)) == 0 {
			outFile += ".g726"
		}
	case PCMA:
		audioCodec = "pcm_alaw"
		format = "alaw"
		if len(filepath.Ext(outFile)) == 0 {
			outFile += ".g711a"
		}
	case PCMU:
		audioCodec = "pcm_mulaw"
		format = "mulaw"
		if len(filepath.Ext(outFile)) == 0 {
			outFile += ".g711u"
		}
	case PCM:
		audioCodec = "pcm_s16le"
		format = "s16le"
		if len(filepath.Ext(outFile)) == 0 {
			outFile += ".pcm"
		}
	}

	{
		dir, file, ext := help.Split(outFile)
		file = fmt.Sprintf("%v_%v_%v_%v_%v_%v", file, sampleRate, channel, audioCodec, format, bitRate)
		outFile = fmt.Sprintf("%v%v%v", dir, file, ext)
	}

	var args []string
	if start > 0 {
		args = append(args, "-ss", help.GetDurationString(start))
	}
	if duration > 0 {
		args = append(args, "-t", help.GetDurationString(duration))
	}
	args = append(args, "-i", inFile)
	if sampleRate > 0 {
		args = append(args, "-ar", strconv.Itoa(sampleRate))
	}
	args = append(args, "-c:a", audioCodec)
	if len(format) > 0 {
		args = append(args, "-f", format)
	}
	if channel > 0 {
		args = append(args, "-ac", strconv.Itoa(channel))
	}
	if len(bitRate) > 0 {
		args = append(args, "-b:a", bitRate)
	}
	args = append(args, "-vn")
	args = append(args, "-y")
	args = append(args, outFile)

	onmessage(colorMessage(strings.Join(append([]string{"ffmpeg"}, args...), " ")))
	cmd := exec.CommandContext(ctx, "ffmpeg", args...)
	r, err := cmd.StderrPipe()
	if err != nil {
		return "", err
	}

	go tis.readConsole(r, onmessage)

	if err := cmd.Run(); err != nil {
		return "", err
	}

	return outFile, nil
}

func (tis *Audio) readConsole(r io.ReadCloser, onmessage func(msg string)) {
	defer r.Close()

	rr := bufio.NewReader(r)
	for {
		ch, err := rr.ReadString('\r')
		if err != nil {
			return
		}

		msg := ch

		msg = strings.TrimLeft(msg, "\r")
		msg = strings.TrimLeft(msg, "\n")

		msg = strings.TrimRight(msg, "\r")
		msg = strings.TrimRight(msg, "\n")
		onmessage(msg)
	}
}
